package com.csailing4cloud.csailing.common.redis.component;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import com.alibaba.fastjson.JSON;
import com.csailing4cloud.csailing.common.core.exception.WarnException;
import com.csailing4cloud.csailing.common.core.util.StreamUtil;
import com.csailing4cloud.csailing.common.core.util.WebUtils;
import com.csailing4cloud.csailing.common.redis.annotation.RateLimiter;
import com.csailing4cloud.csailing.common.redis.annotation.RateLimiterDetail;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author dyh
 * 限流器
 */
@Aspect
@Slf4j
@Order(2)
public class RateLimiterAspect {

    @Autowired
    private RedisRateLimiter redisRateLimiter;

    @Around("execution(public * *..controller..*.*(..)) && @annotation(rateLimiter)" )
    public Object rateLimit(ProceedingJoinPoint pPoint, RateLimiter rateLimiter) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        RateLimiterDetail[] rateLimiterDetails = rateLimiter.rateLimiterDetails();
        for (RateLimiterDetail rateLimiterDetail : rateLimiterDetails) {
            doRateLimit(pPoint, rateLimiterDetail, request);
        }
        return pPoint.proceed(pPoint.getArgs());
    }

    /**
     * 限流
     * @param pPoint
     * @param rateLimiterDetail
     * @param request
     */
    private void doRateLimit(ProceedingJoinPoint pPoint, RateLimiterDetail rateLimiterDetail, HttpServletRequest request) {
        boolean success = true;
        if (rateLimiterDetail.base() == RateLimiterDetail.Base.General) {
            //根据path限流
            success = doLimitByPath(rateLimiterDetail, request);
        } else if (rateLimiterDetail.base() == RateLimiterDetail.Base.IP) {
            //根据ip限流
            success = doLimitByIp(rateLimiterDetail);
        } else if (rateLimiterDetail.base() == RateLimiterDetail.Base.Args) {
            //根据参数限流
            success = doLimitByArgs(pPoint, rateLimiterDetail);
        }
        if(!success){
            //序列化时过滤掉request和response
            List<Object> logArgs = StreamUtil.streamOf(pPoint.getArgs())
                    .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
                    .collect(Collectors.toList());
            log.info("限流器拦截请求,拦截详情:{}，参数:{}",rateLimiterDetail, JSON.toJSONString(logArgs));
            throw new WarnException("您的请求正在处理中，请稍后");
        }
    }

    /**
     * 根据参数限流
     * @param pPoint
     * @param rateLimiterDetail
     * @return
     */
    private boolean doLimitByArgs(ProceedingJoinPoint pPoint, RateLimiterDetail rateLimiterDetail) {
        boolean success = true;
        String[] argNames = rateLimiterDetail.argNames();
        if(ArrayUtil.isNotEmpty(argNames)){
            List<String> argNameList = Arrays.asList(argNames);
            Object[] args = pPoint.getArgs();
            StringBuffer stringBuffer = new StringBuffer();
            for (Object arg : args) {
                Field[] fields = ReflectUtil.getFields(arg.getClass());
                if(ArrayUtil.isNotEmpty(fields)){
                    for (Field field : fields) {
                        if(argNameList.contains(field.getName())){
                            Object value = BeanUtil.getFieldValue(arg,field.getName());
                            if(Objects.nonNull(value)){
                                stringBuffer.append(value);
                                stringBuffer.append("_");
                            }

                        }
                    }
                }
            }
            String str = stringBuffer.toString();
            if(StringUtils.isNotBlank(str)){
                String keyPrefix = str.substring(0, str.length() - 1);
                success = redisRateLimiter.acquire(keyPrefix, rateLimiterDetail);
            }
        }
        return success;
    }


    /**
     * 根据ip限流
     * @param rateLimiterDetail
     * @return
     */
    private boolean doLimitByIp(RateLimiterDetail rateLimiterDetail) {
        boolean success = true;
        //根据ip限流
        String ip = WebUtils.getIP();
        if (ip != null) {
            success = redisRateLimiter.acquire(ip, rateLimiterDetail);
        }
        return success;
    }

    /**
     * 根据path限流
     * @param rateLimiterDetail
     * @param request
     * @return
     */
    private boolean doLimitByPath(RateLimiterDetail rateLimiterDetail, HttpServletRequest request) {
        String path = rateLimiterDetail.path();
        if (StringUtils.isBlank(path)) {
            path = request.getRequestURI();
        }
        boolean success;
        String rateLimiterKey = path;
        success = redisRateLimiter.acquire(rateLimiterKey, rateLimiterDetail);
        return success;
    }


}
